﻿namespace CloudNinja.Metering.Providers.AzureBlob
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    using CloudNinja.Metering.Common;
    using CloudNinja.Metering.Common.Configuration;
    using CloudNinja.Metering.Common.Logging;
    using CloudNinja.Metering.Data;

    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.Storage;
    using Microsoft.WindowsAzure.Storage.Auth;

    using XRay.Library;
    using XRay.Library.AzureStorage;
    using XRay.Library.Contracts;

    using MeterData = CloudNinja.Metering.Data.Models.MeterData;

    [ProviderConfigElementType("CloudNinja.Metering.Providers.Configuration.BlobBandwidth.BlobBandwidthProviderElement, CloudNinja.Metering.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public class BlobBandwidthProvider : MeteringProviderBase
    {
        private readonly Dictionary<string, TenantsStorageAccount> storageAccounts;

        private readonly IParserBookmarkRepository parserBookmarkRepository;

        public BlobBandwidthProvider(
            TimeSpan collectInterval,
            Dictionary<string, TenantsStorageAccount> storageAccounts,
            ILogger logger,
            IParserBookmarkRepository parserBookmarkRepository,
            IMeteringRepository repository,
            IMeteringContext meteringContext)
            : base(collectInterval, repository, logger, meteringContext)
        {
            Helper.Guard(collectInterval, "collectInterval");
            Helper.Guard(storageAccounts, "storageAccounts");
            Helper.Guard(parserBookmarkRepository, "parserBookmarkRepository");

            this.storageAccounts = storageAccounts;
            this.parserBookmarkRepository = parserBookmarkRepository;
        }

        public override string Name
        {
            get { return "BlobBandwidthProvider"; }
        }

        public override void Initialize()
        {
            this.Logger.LogInformation("Initialized BlobBandwidth Provider");
        }

        public override void Collect()
        {
            var results = new Dictionary<string, long>();
            var lockObject = new object();

            this.StartTiming();
            using (var sem = new SemaphoreSlim(1))
            {
                Parallel.ForEach(
                    this.storageAccounts.Keys,
                    () => new Dictionary<string, long>(),
                    (storageAccountName, loopstate, localDict) =>
                    {
                        var latestBookmark = this.GetLatestBookmark(storageAccountName, this.storageAccounts[storageAccountName]);

                        var endTime = DateTimeOffset.UtcNow;
                        CloudStorageAccount storageAccount;

                        if (!CloudStorageAccount.TryParse(this.storageAccounts[storageAccountName].ConnectionString, out storageAccount))
                        {
                            this.Logger.LogError(
                                string.Format(
                                    "Invalid storage account connection string: {0}",
                                    this.storageAccounts[storageAccountName].ConnectionString));
                        }

                        var storageAnalyticsClient = new BlobAnalyticsClient(new AzureCloudStorageAccount(storageAccount));

                        var logEntities = storageAnalyticsClient.GetLogEntities(latestBookmark, endTime);

                        var groupedLogEntries = (from logEntity in logEntities
                                                 join details in AnalyticsFieldNamesHelper.StatusMessageDetailsDictionary on logEntity.RequestStatus equals details.Key
                                                 where logEntity.ServiceType.Equals(AzureStorageServiceEnum.Blob) && details.Value.Billable
                                                 group logEntity by Helper.GetTenantId(new Uri(logEntity.RequestURL).Segments.Last(), this.storageAccounts[storageAccountName].TenantIdPattern)
                                                     into hourlyOperationGroups
                                                     select new
                                                         {
                                                             tenant = hourlyOperationGroups.Key,
                                                             bandwidth = hourlyOperationGroups.Sum(hg => hg.ResponsePacketSize)
                                                         })
                                                 .ToDictionary(x => x.tenant, x => x.bandwidth);

                        return groupedLogEntries;
                    },
                    finalResult =>
                    {
                        lock (lockObject)
                        {
                            foreach (var l in finalResult)
                            {
                                results.Add(l.Key, l.Value);
                            }
                        }
                    });
            }

            this.MeteringRepository.Write(results.Select(x => new MeterData
                {
                    MeterTypeId = "bbw",
                    TenantId = x.Key,
                    Value = x.Value,
                    TimePeriod = DateTime.UtcNow,
                    Count = 0
                }));

            foreach (var tenantsStorageAccount in this.storageAccounts)
            {
                this.parserBookmarkRepository.SetBookmark(new ParserBookmark(this.Name, tenantsStorageAccount.Key, DateTime.UtcNow, 0));
            }

            this.StopTiming();
        }

        public override void Stop()
        {
            // we do not need to do anything here.
        }

        private DateTimeOffset GetLatestBookmark(string storageAccountName, TenantsStorageAccount tenantsStorageAccount)
        {
            var bookMark = this.parserBookmarkRepository.GetBookmark(this.Name, storageAccountName);

            if (bookMark != null)
            {
                return new DateTimeOffset(bookMark.LastTimeParsed);
            }

            CloudStorageAccount storageAccount;
            if (!CloudStorageAccount.TryParse(tenantsStorageAccount.ConnectionString, out storageAccount))
            {
                this.Logger.LogError(string.Format("Invalid tenant connection string {0}", tenantsStorageAccount.ConnectionString));
                return DateTimeOffset.MaxValue;
            }
            var analyticsClient = new BlobAnalyticsClient(new AzureCloudStorageAccount(storageAccount));
            return analyticsClient.GetEarliestLogHour();
        }
    }
}
